/*
根据CTCMaster.h的定义,此文件为CTC协议主控端的演示文件.用于卡头IO板程序
使用时需要将CTCBuffer.h,CTCClient.h,CTCMaster.h,CTCMsgV1.h,Debug.h文件添加到工程中,并将CTCMaster.c,CTCBuffer.c文件添加到工程中编译
调试输出信息可参考Debug.h文件定义顶部的说明
*/

#include "CTCMaster.h"
#include "stm32f1xx_hal.h"
#include <stm32f1xx_hal_uart.h>

static uint8_t masterrx_data;     // 用于中断接收单个字节
UART_HandleTypeDef huart1;

// 系统时钟配置
void SystemClock_Config(void);

// 串口配置
void MasterApp_USART_Config(uint32_t baudrate)
{
    /* 串口基本参数配置 */
    huart1.Instance          = USART1;
    huart1.Init.BaudRate     = baudrate;             // 波特率
    huart1.Init.WordLength   = UART_WORDLENGTH_8B;   // 8位数据位
    huart1.Init.StopBits     = UART_STOPBITS_1;      // 1位停止位
    huart1.Init.Parity       = UART_PARITY_NONE;     // 无奇偶校验
    huart1.Init.Mode         = UART_MODE_TX_RX;      // 收发模式
    huart1.Init.HwFlowCtl    = UART_HWCONTROL_NONE;  // 无硬件流控
    huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 16倍过采样

    /* 初始化串口 */
    HAL_UART_Init(&huart1);
}

void Error_Handler(void)
{
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1) {
    }
    /* USER CODE END Error_Handler_Debug */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState       = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
     */
    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1) {
        HAL_IncTick();
    }
}

/* UART MSP初始化函数,配置串口IO脚 */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* USART1引脚配置: PA9(TX), PA10(RX) */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1中断配置 */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  }
}







// 打印数据
void Debug_PrintData(const char *prefix, uint8_t *data, uint16_t len)
{
    #if DEBUG_LEVEL > 0
    SEGGER_RTT_printf(0, "%s:", prefix);
    for (uint16_t i = 0; i < len; i++) {
        SEGGER_RTT_printf(0, "%02X ", data[i]);
    }
    SEGGER_RTT_printf(0, "\r\n");
    #endif
}

/// @brief 串口发送数据回调
/// @param data 指向要发送的数据指针
/// @param len 数据长度
void MasterApp_OnSendData(uint8_t *data, uint16_t len)
{
    // 这里实现通过串口发送数据的代码
    //使用物理串口发送数据,超时100ms
    Debug_PrintData("UART Send", data, len);
    HAL_UART_Transmit(&huart1, data, len, 100);
}

/// @brief 串口接收数据中断,需覆盖系统串口消息接收中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 这里实现通过串口接收数据的代码
    if (huart->Instance == USART1) {
        CTC_Master_ReceiveData(&masterrx_data, 1); // 将接收到的字节写入接收缓冲区
        // 重新启动中断接收
        HAL_UART_Receive_IT(&huart1, &masterrx_data, 1);
    }
}

// 主控端消息发送结果回调
/// @brief 数据发送结果的回调
/// @param result 发送结果,参考CTC_AckResult
/// @param cmdid 命令ID,参考CTC_CMD_XXX
/// @param transactionId 事务ID,与发送时的事务ID相同
/// @return 返回后续处理动作,参考CTC_ClientSendAction
CTC_MsgReSendAction MasterApp_OnMasterSendDataResult(CTC_AckResult result, uint16_t cmdid, uint32_t transactionId)
{
    // 这里接收的只会有几种result结果
    // CTC_ACK_SUCCESS 发送成功,一定删除发送缓冲不会再重发
    // CTC_ACK_UNSUPPORTED 不支持的指令,需要检查协议版本,一定删除发送缓冲不会再重发
    // CTC_ACK_LENGTH_ERR 长度错误,需要检查协议版本和数据是否符合规范,一定删除发送缓冲不会再重发
    // CTC_ACK_TIMEOUT 发送超时,根据返回的结果判断是丢弃还是重发
    if (result == CTC_ACK_SUCCESS) {
        // 消息发送成功
        //DEBUG_INFO("CTCTEST Master Send Data Success,TransactionID: %d\r\n", transactionId);
    } else if (result == CTC_ACK_TIMEOUT) {
        // 消息发送超时了,需要自己判断是否重发
        // 如果是关键数据,可以选择重发 返回CTC_TIMEOUT_DATAACT_RESEND
        // 如果是非关键数据,可以选择删除 返回CTC_TIMEOUT_DATAACT_MISS
        DEBUG_WARN("CTCTEST Master Send Data Timeout,TransactionID: %d\r\n", transactionId);
        return CTC_TIMEOUT_DATAACT_RESEND; // 重发此消息
    }
    return CTC_TIMEOUT_DATAACT_MISS; // 默认删除此消息
}

/// @brief 连接状态变化,设备故障状态,可消费状态变化回调
/// @param state 当前连接状态,参考CTC_ClientConnState
/// @param prevState 上一个连接状态,参考CTC_ClientConnState
void MasterApp_OnMasterConnStateChange(CTC_ClientConnState state, CTC_ClientConnState prevState)
{
    // 这里处理连接状态变化的代码
    if (state == CTC_CLIENT_CONNSTATE_CONNECTED && prevState != CTC_CLIENT_CONNSTATE_CONNECTED) {
        // 连接成功
        DEBUG_INFO("CTCTEST Master Connected to Slave\r\n");
    } else if (state == CTC_CLIENT_CONNSTATE_CONNECTTING && prevState != CTC_CLIENT_CONNSTATE_CONNECTTING) {
        // 丢失了连接,正在尝试重连
        DEBUG_INFO("CTCTEST Master Connecting to Slave...\r\n");
    } else if (state == CTC_CLIENT_CONNSTATE_OFFLINE) {
        // CTC1.0不会出现此状态,如果出现说明参数不足无法建立连接
        DEBUG_ERROR("CTCTEST Master Offline,Check Parameters\r\n");
    }
    // 此时可以获取从设备状态和是否可消费信息
    uint8_t slavestate = CTC_Master_GetSlaveDeviceState();
    uint8_t consumable = CTC_Master_IsSlaveConsumable();

    if (slavestate == CTC_STATE_UNKNOWN) {
        // 从未连成功过
        DEBUG_WARN("CTCTEST Slave device state is unknown.\r\n");
    } else {
        if (slavestate == 0) {
            // 状态为0,表示设备无故障
        } else {
            // 状态非0,表示设备有故障,具体含义参考从设备的状态定义,可以将此信息提交到后台进行报警提醒工作人员处理
        }
        DEBUG_INFO("CTCTEST Slave device state: %d, Consumable: %d\r\n", slavestate, consumable);
    }
    if (consumable == 0) {
        // 可以消费,意思是可以接受投币等操作
        DEBUG_INFO("CTCTEST Slave device is consumable.\r\n");
    } else {
        // 不可消费,游戏机可能在初始化状态或故障状态,不允许投币等操作
        DEBUG_WARN("CTCTEST Slave device is not consumable.\r\n");
    }
}

// Master收到抄表结果回调
void MasterApp_OnMasterMeterRead(const CTC_MeterDataRspMsgV1 *msg)
{
    // 在调用CTC_Master_SendReadMeterCommand后,会收到此回调
    // 这里处理抄表结果,一般将数据提交给后台系统作为核对数据使用
    DEBUG_INFO("CTCTEST Master Meter Read:\r\n");
    for (uint8_t i = 0; i < 10; i++) {
        if (msg->Items[i].MeterType == CTC_METER_TYPE_END) break; // 结束
        DEBUG_INFO("  ItemId: %d, Value: %d\r\n", msg->Items[i].MeterType, msg->Items[i].MeterValue);
        if (msg->Items[i].MeterType == CTC_METER_TYPE_COIN_IN_TOTAL) {
            // 总投币数
            DEBUG_INFO("CTCTEST totalcoinin meter: %d\r\n", msg->Items[i].MeterValue);
        } else if (msg->Items[i].MeterType == CTC_METER_TYPE_TICKET_TOTAL) {
            // 总出彩票数
            DEBUG_INFO("CTCTEST totalticketout meter: %d\r\n", msg->Items[i].MeterValue);
        } else if (msg->Items[i].MeterType == CTC_METER_TYPE_BLUE_TICKET_TOTAL) {
            // 总出蓝票数
            DEBUG_INFO("CTCTEST totalblueout meter: %d\r\n", msg->Items[i].MeterValue);
        } else if (msg->Items[i].MeterType == CTC_METER_TYPE_PHYSICAL_REWARD1_TOTAL) {
            // 总出礼品数
            DEBUG_INFO("CTCTEST totalgift meter: %d\r\n", msg->Items[i].MeterValue);
        }
    }
}

// Master收到游戏奖励回调
void MasterApp_OnMasterGameReward(const CTC_RewardMsgV1 *msg)
{
    // 游戏机有出电子奖励物时调用此函数,如出彩票,出蓝票
    // 这里一般将数据保存到会员账户
    DEBUG_INFO("CTCTEST Master Game Reward:\r\n");
    DEBUG_INFO("  RewardType: %d, RewardCount: %d\r\n", msg->RewardType, msg->RewardCount);
    if (msg->RewardType == CTC_REWARD_TYPE_TICKET) {
        // 出彩票
        DEBUG_INFO("CTCTEST ticket reward: %d\r\n", msg->RewardCount);
    } else if (msg->RewardType == CTC_REWARD_TYPE_BLUETICKET) {
        // 出蓝票
        DEBUG_INFO("CTCTEST blue ticket reward: %d\r\n", msg->RewardCount);
    } else if (msg->RewardType == CTC_REWARD_TYPE_SCORE) {
        // 游戏得分
        DEBUG_INFO("CTCTEST score reward: %d\r\n", msg->RewardCount);
    }
}

// Master收到氛围事件回调
void MasterApp_OnMasterAtmosphereEvent(const CTC_AtmosphereDataV1 *msg)
{
    // 游戏机有氛围事件时调用此函数,如中了JP大奖
    // 这里一般触发氛围灯光或音效
    DEBUG_INFO("CTCTEST Master Atmosphere Event:\r\n");
    DEBUG_INFO("  EventLevel: %d, RewardType: %d, RewardCount: %d\r\n", msg->EventLevel, msg->RewardType, msg->RewardCount);
}

// Master收到实物奖励回调
void MasterApp_OnMasterPhysicalReward(const CTC_PhysicalReportMsgV1 *msg)
{
    // 游戏机有实物奖励结果时调用此函数,如出实物币,出实物彩票,出实物礼品等
    // 这里一般将数据保存到管理系统后台方便核对
    DEBUG_INFO("CTCTEST Master Physical Reward Report:\r\n");
    DEBUG_INFO("  PhysicalType: %d, PhysicalCount: %d, Result: %d\r\n", msg->PhysicalType, msg->PhysicalCount);
    if (msg->PhysicalType == CTC_PHYSICAL_REWARD_TYPE_REALCOIN) {
        // 出实物币
        DEBUG_INFO("CTCTEST physical real coin reward: %d\r\n", msg->PhysicalCount);
    } else if (msg->PhysicalType == CTC_PHYSICAL_REWARD_TYPE_REALTICKET) {
        // 出实物彩票
        DEBUG_INFO("CTCTEST physical real ticket reward: %d\r\n", msg->PhysicalCount);
    } else if (msg->PhysicalType == CTC_PHYSICAL_REWARD_TYPE_REALBLUETICKET) {
        // 出实物蓝票
        DEBUG_INFO("CTCTEST physical real blue ticket reward: %d\r\n", msg->PhysicalCount);
    } else if (msg->PhysicalType == CTC_PHYSICAL_REWARD_TYPE_GIFT1) {
        // 出实物礼品
        DEBUG_INFO("CTCTEST physical real gift reward: %d\r\n", msg->PhysicalCount);
    }
}

// Master收到单价查询结果回调
void MasterApp_OnMasterQueryCoinPriceRsp(const CTC_CoinPriceMsgV1 *msg)
{
    // 在调用CTC_Master_SendQueryCoinPriceCommand后,会收到此回调
    // 这里处理单价查询结果,一般将数据提交给后台系统核对是否需要更改单价
    DEBUG_INFO("CTCTEST Master Query Coin Price Rsp:\r\n");
    DEBUG_INFO("  CoinPrice: %d\r\n", msg->CoinPerGame);
}

//投币响应回调
void MasterApp_OnMasterCoinInResponse(const CTC_CoinInRspMsgV1 *msg)
{
    // 在调用CTC_Master_SendCoinInCommand后,会收到此回调
    // 这里处理投币响应结果,一般将数据提交给后台系统核对投币结果
    DEBUG_INFO("CTCTEST Master Coin In Response:\r\n");
    DEBUG_INFO("  Result: %d\r\n", msg->Result);
    //如果结果为1表示投币失败,需要将投币退回给会员
}

void MasterApp_OtherTask(void)
{
     // 发送抄表命令
     //如果超时未收到回应,会自动重发3次,然后通过MasterApp_OnMasterSendDataResult回调通知发送发超时询问是否重发
     //如果发送成功会通过MasterApp_OnMasterSendDataResult回调通知发送成功
     //如果收到回应会通过MasterApp_OnMasterMeterRead回调通知抄表结果
    CTC_Master_SendReadMeterCommand();
    
    // 发送查询单价命令
    //如果超时未收到回应,会自动重发3次,然后通过MasterApp_OnMasterSendDataResult回调通知发送发超时询问是否重发
    //如果发送成功会通过MasterApp_OnMasterSendDataResult回调通知发送成功
    //如果收到回应会通过MasterApp_OnMasterQueryCoinPriceRsp回调通知查询结果
    CTC_Master_SendQueryCoinPriceCommand();

    // 发送设置单价命令
    //如果对方已收到会通过MasterApp_OnMasterSendDataResult回调通知发送成功
    CTC_CoinPriceMsgV1 price;
    price.CoinPerGame = 5; // 设置5个币每局
    CTC_Master_SendSetCoinPriceCommand(&price);

    //通道切换
    //如果对方已收到会通过MasterApp_OnMasterSendDataResult回调通知发送成功
    CTC_ChannelSelMsgV1 chsel;
    chsel.Ticket       = 0; // 切换到0或1
    chsel.BlueTicket   = 1; // 切换到0或1
    CTC_Master_SendSelectChannelCommand(&chsel);

    //投币
    //如果对方已收到会通过MasterApp_OnMasterSendDataResult回调通知发送成功
    //如果对方响应此消息会通过MasterApp_OnMasterCoinInResponse回调通知投币结果
    CTC_CoinInMsgV1 coinin;
    coinin.CoinCount = 100; // 投币数量
    coinin.MemberId[0] = 1; // 会员ID用于游戏机识别会员唯一
    coinin.RecordId[0] = 1; // 消费记录ID,用于回应是哪一笔投币
    CTC_Master_SendCoinInCommand(&coinin);


    //设置设备状态
    //设置后会通过心跳实时传递给从设备
    CTC_Master_SetMasterState(CTC_MASTER_STATE_IDLE); // 设置主控端状态为空闲,此状态才允许游戏机上报数据
    CTC_Master_SetMasterState(CTC_MASTER_STATE_WAITCONFIG);// 设置主控端状态为等待配置

    //获取从设备状态
    uint8_t slavestate = CTC_Master_GetSlaveDeviceState();// 获取从设备状态
    uint8_t consumable = CTC_Master_IsSlaveConsumable();// 获取从设备状态和是否可消费信息
}

// 示例主函数
int main(void)
{
    // 初始化硬件
    HAL_Init();           // 初始化HAL库
    SystemClock_Config(); // 配置系统时钟
    MasterApp_USART_Config(38400); // 初始化串口,CTC协议波特率38400

    #if DEBUG_LEVEL > 0
    // 初始化SEGGER RTT用于调试输出
    SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);     /* 配置通道 0，上行配置*/
    SEGGER_RTT_ConfigDownBuffer(0, "RTTDOWN", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); /* 配置通道 0，下行配置*/
    SEGGER_RTT_SetTerminal(0);      
    #endif                                                   // 设置打印通道0

    // 初始化CTC主控端
    CTC_Master_Init();
    // 设置回调函数
    CTC_Master_SetSendDataCallback(MasterApp_OnSendData);                // 设置主控端数据发送函数
    CTC_Master_SetDataSendResultCallback(MasterApp_OnMasterSendDataResult);    // 设置主控端数据发送结果回调
    CTC_Master_SetSlaveStateChangeCallback(MasterApp_OnMasterConnStateChange); // 设置主控端连接状态变化回调
    CTC_Master_SetReadMeterCallback(MasterApp_OnMasterMeterRead);              // 设置主控端抄表结果回调
    CTC_Master_SetGameRewardEventCallback(MasterApp_OnMasterGameReward);       // 设置主控端游戏奖励回调
    CTC_Master_SetAtmosphereEventCallback(MasterApp_OnMasterAtmosphereEvent);  // 设置主控端氛围事件回调
    CTC_Master_SetPhysicalRewardCallback(MasterApp_OnMasterPhysicalReward);    // 设置主控端实物奖励回调
    CTC_Master_SetQueryCoinPriceCallback(MasterApp_OnMasterQueryCoinPriceRsp);    // 设置主控端单价查询结果回调
    CTC_Master_SetCoinInRspCallback(MasterApp_OnMasterCoinInResponse);      // 设置主控端投币响应回调

    // 启动串口中断接收,每个字节都触发中断
    HAL_UART_Receive_IT(&huart1, &masterrx_data, 1);

    // 主循环
    while (1) {
        // 每1ms调用一次主循环处理函数
        CTC_Master_Loop1ms();
        // 其他任务处理
        //MasterApp_OtherTask();
        HAL_Delay(1); // 延时1ms
    }
}
